using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Reflection;
using Google.Protobuf;
using Google.Protobuf.Reflection;
using HuaXianQu.ProtoBuffEx.Runtime;
using UnityEngine;

namespace HuaXianQu.ProtoBuffEx.ToolEditor
{
    public class ClassListGen : IProtoToClassParse, IProtoGenParserCS
    {
        public ClassListGen(ExcelToCsHarp.CSStruct csCtruct)
        {
            this.csCtruct = csCtruct;
            IsDataClass = true;
        }

        class GenParseJsInfo
        {
            public string MapName;
            public List<VariableInfo.ExcelVariableInfoAttribute> ExcelVariableInfo = new();
        }

        private string _dictionaryName = "ConcurrentDictionary<{0},{1}>";

        /// <summary>
        /// excel功能总行数
        /// </summary>
        public const int FunctionRows = 5;

        /// <summary>
        /// proto变量编号所在行
        /// </summary>
        public const int excelVariable = 0;

        /// <summary>
        /// proto变量编号所在行
        /// </summary>
        public const int protoCodeIndex = 1;

        /// <summary>
        /// proto变量类型所在行
        /// </summary>
        public const int protoTypeRow = 3;

        /// <summary>
        /// proto变量名称所在行
        /// </summary>
        public const int protoVarableNameRow = 4;

        private DataTable dataTable { get; set; }
        public const string ClassFeildName = "_class";

        public string ClassName { get; set; }
        private int FeildCount { get; set; }

        private IMessage excelData { get; set; }
        private string DataaExe = "protodat";
        private string dataPath => ProtoPathConfig.Inst.GenerateProtoDataPath;
        private string parserPath => $"{ProtoPathConfig.Inst.GenerateProtoCsRootPath}/ProtoParser";

        private string DataItemName => $"{ClassName}Item";
        public const string DataListName = "DataList";
        public const string DataDictName = "DataDict";
        public const string VariableAttributeMapName = "VariableAttributeMap";
        public const string ExcelVariableAttributeName = "ExcelVariableInfoAll";
        public const string VariableInfoAllMap = "VariableInfoAllMap";

        private Dictionary<string, VariableInfo> variableInfoDict;
        private Dictionary<int, VariableInfo> variableInfoByIndexDict;
        private List<VariableInfo> variableInfos;
        private ClassGen classGen;

        /// <summary>
        /// 所有自定义类变量的所有变量
        /// </summary>
        public List<VariableInfo> ClassInfoAlls;

        private ExcelToCsHarp.CSStruct csCtruct;

        /// <summary>
        /// 记录excel每一行的列数据
        /// </summary>
        List<ExcelData> dataValues = new();

        /// <summary>
        /// excel每行数据数据转成proto的Item数据
        /// </summary>
        List<IMessage> itemValues = new();

        /// <summary>
        /// 解析excel表格
        /// </summary>
        /// <param name="table"></param>
        public void Parse(DataTable table)
        {
            dataTable = table;
            ClassName = table.TableName;
            FeildCount = table.Columns.Count;
            variableInfos = new();
            ClassInfoAlls = new();
            variableInfoDict = new();
            variableInfoByIndexDict = new();
            CurObjectInfo.CustomVariableNameList.Add(ClassName);
            CurObjectInfo.CustomVariableNameList.Add(DataItemName);
            for (int i = 0; i < FeildCount; i++)
            {
                string index = table.Rows[protoCodeIndex][i].ToString();
                if (string.IsNullOrEmpty(index))
                {
                    ProtoDebug.Log($"配置表{ClassName} 第{protoCodeIndex}行，第{i + 1}列没有配置数据");
                    continue;
                }

                string variableType = table.Rows[protoTypeRow][i].ToString();
                string variableName = table.Rows[protoVarableNameRow][i].ToString();
                if (string.IsNullOrEmpty(variableType) || string.IsNullOrEmpty(variableName))
                {
                    continue;
                }

                if (int.TryParse(index, out int protoIndex))
                {
                    VariableInfo info =
                        new VariableInfo()
                        {
                            ProtoIndex = protoIndex,
                            OriginVariableType = variableType,
                            DataIndex = i,
                            ExcelDataOrInfo = table.Columns,
                            ExcelVariableInfo = table.Rows[excelVariable][i].ToString(),
                        };
                    info.SetName(variableName);
                    info.Parse();
                    info.ParseExcelVariableInfo();
                    if (info.IsExistsCustomClass())
                    {
                        CurObjectInfo.ReferenceOfVariableNameList.Add(info.CustomVariableName);
                    }

                    if (variableInfoDict.TryGetValue(info.VariableName, out var temp))
                    {
                        info.AddChild(info);
                    }
                    else
                    {
                        variableInfos.Add(info);
                        info.GetClassInfo(ref ClassInfoAlls);
                        variableInfoDict.Add(info.VariableName, info);
                    }

                    variableInfoByIndexDict.Add(info.DataIndex, info);
                }
            }

            {
                //检查类名是否重复
                HashSet<string> classSet = new HashSet<string>();
                for (int i = 0; i < ClassInfoAlls.Count; i++)
                {
                    string name = ClassInfoAlls[i].VariableName;
                    bool isExist = classSet.Contains(name);
                    if (!isExist)
                    {
                        classSet.Add(name);
                    }
                    else
                    {
                        ProtoDebug.LogError($"存在相同类名：{name}");
                        return;
                    }
                }
            }
        }

        /// <summary>
        /// 设置数据,将每一行数据转换成一个Item类数据
        /// </summary>
        public void SetData()
        {
            itemValues.Clear();
            classGen = new();
            Dictionary<string, ExcelData> datas = new();
            for (int i = FunctionRows; i < dataTable.Rows.Count; i++)
            {
                dataValues.Clear();
                datas.Clear();
                for (int j = 0; j < FeildCount; j++)
                {
                    var obj = dataTable.Rows[i][j].ToString();
                    if (!variableInfoByIndexDict.TryGetValue(j, out VariableInfo variableInfo))
                    {
                        ProtoDebug.LogError($"类{ClassName}数据解析错误,字段为：{obj}");
                        continue;
                    }

                    var dataTemp = ExcelData.GetExcelData(this);
                    dataTemp.Data = obj;
                    dataTemp.DataIndex = j;
                    dataTemp.CurVariableInfo = variableInfo;
                    if (variableInfo.IsMember)
                    {
                        if (!datas.TryGetValue(variableInfo.VariableName, out var data))
                        {
                            var classData = ExcelData.GetExcelData(this);
                            //classData.Data = obj;
                            //classData.DataIndex = j;
                            //classData.CurVariableInfo = variableInfo;
                            classData.AddChild(dataTemp);
                            datas.Add(variableInfo.VariableName, classData);
                            dataValues.Add(classData);
                        }
                        else
                        {
                            data.AddChild(dataTemp);
                        }
                    }
                    else
                    {
                        dataValues.Add(dataTemp);
                    }
                }

                if (ProtoBuffAssembleEditor.GetProtoBuffStatic(DataItemName, out IMessage itemMessage, out Type type))
                {
                    if (classGen.SetProtoBuffDataToClass(ref itemMessage, null, dataValues))
                    {
                        itemValues.Add(itemMessage);
                    }
                }
            }

            SaveProtoBuff(out IMessage message, itemValues);
            SetProtoBuffDictionary(out IMessage message1, itemValues);
            if (message == null)
            {
                ProtoDebug.LogError($"类{ClassName}没有找到,查看配置表是否正确");
                return;
            }

            SetVariableAttributeMap(ref message);
            excelData = message;
        }


        /// <summary>
        /// 保存proto数据
        /// </summary>
        /// <param name="message"></param>
        /// <param name="values"></param>
        public void SaveProtoBuff(out IMessage message, List<IMessage> values)
        {
            if (ProtoBuffAssembleEditor.GetProtoBuffStatic(ClassName, out message, out Type type))
            {
                PropertyInfo propertyInfo = type.GetProperty(DataListName);
                if (propertyInfo == null)
                {
                    ProtoDebug.LogError($"类{ClassName}没有{DataListName}属性");
                    return;
                }

                object list = propertyInfo.GetValue(message);
                IList field = (IList)list;
                foreach (var value in values)
                {
                    field.Add(value);
                }
            }
            else
            {
                ProtoDebug.LogError($"类{ClassName}没有找到");
            }
        }

        public void SetProtoBuffDictionary(out IMessage message, List<IMessage> values)
        {
            if (ProtoBuffAssembleEditor.GetProtoBuffStatic(ClassName, out message, out Type type))
            {
                PropertyInfo propertyInfo = type.GetProperty(DataDictName);
                if (propertyInfo == null)
                {
                    ProtoDebug.LogError($"类{ClassName}没有{DataDictName}属性");
                    return;
                }

                object dict = propertyInfo.GetValue(message);
                IDictionary field = (IDictionary)dict;
                foreach (var value in values)
                {
                    FieldDescriptor fieldDescriptor = value.Descriptor.Fields[1];
                    if (fieldDescriptor==null)
                    {
                        return;
                    }
                    if (!field.Contains(value))
                    {
                        field.Add(fieldDescriptor.Accessor.GetValue(value), value);
                    }
                }
            }
            else
            {
                ProtoDebug.LogError($"类{ClassName}没有找到");
            }
        }

        /// <summary>
        /// 设置变量特性信息
        /// </summary>
        /// <param name="message"></param>
        private void SetVariableAttributeMap(ref IMessage message)
        {
            FieldDescriptor variableInfoAllMapDescriptor =
                message.Descriptor.FindFieldByName(VariableInfoAllMap);
            object attriable = variableInfoAllMapDescriptor.Accessor.GetValue(message);
            IDictionary dictionary =
                (IDictionary)attriable;

            if (!ProtoBuffAssembleEditor.GetProtoBuffStatic(ExcelVariableAttributeName,
                    out IMessage excelVariableAttribute,
                    out Type arrtributetype))
            {
                return;
            }

            foreach (var info in variableInfos)
            {
                if (info.CurExcelVariableInfoAll != null)
                {
                    VariableInfo.ExcelVariableInfoAll attriableInfoAttribute =
                        info.CurExcelVariableInfoAll;

                    FieldDescriptor fieldInfo = excelVariableAttribute.Descriptor.FindFieldByName("ExcelVariableName");

                    fieldInfo.Accessor.SetValue(excelVariableAttribute, info.VariableName);

                    FieldDescriptor excelVariableMapField =
                        excelVariableAttribute.Descriptor.FindFieldByName("ExcelVariableMap");

                    IDictionary variableInfoAllMap =
                        (IDictionary)excelVariableMapField.Accessor.GetValue(excelVariableAttribute);
                    var mapTypeList = excelVariableMapField.MessageType.Fields.InFieldNumberOrder();
                    FieldDescriptor mapType = mapTypeList[1];
                    Type excelVariableInfoAllType = mapType.MessageType.ClrType;
                    IMessage excelVariableInfoAllValue = (IMessage)Activator.CreateInstance(excelVariableInfoAllType);

                    FieldDescriptor excelVariableName =
                        excelVariableInfoAllValue.Descriptor.FindFieldByName("ExcelVariableName");
                    excelVariableName.Accessor.SetValue(excelVariableInfoAllValue,
                        attriableInfoAttribute.VariableName);


                    variableInfoAllMap.Add(attriableInfoAttribute.VariableName, excelVariableInfoAllValue);


                    foreach (var mapInfo in attriableInfoAttribute.ExcelVariableMap)
                    {
                        FieldDescriptor member = excelVariableInfoAllValue.Descriptor.FindFieldByName("IsPrimaryKey");
                        member.Accessor.SetValue(excelVariableInfoAllValue, mapInfo.Value.IsPrimaryKey);
                        member = mapType.MessageType.FindFieldByName("ExcelVariableName");
                        if (string.IsNullOrEmpty(mapInfo.Value.ExcelMapVariableName))
                        {
                            member.Accessor.SetValue(excelVariableInfoAllValue, mapInfo.Value.ExcelMapVariableName);
                        }

                        member = mapType.MessageType.FindFieldByName("SortIndex");
                        member.Accessor.SetValue(excelVariableInfoAllValue, mapInfo.Value.SortIndex);
                        if (!string.IsNullOrEmpty(mapInfo.Value.ExcelVariableValue))
                        {
                            member = mapType.MessageType.FindFieldByName("ExcelVariableValue");
                            member.Accessor.SetValue(excelVariableInfoAllValue, mapInfo.Value.ExcelVariableValue);
                        }
                    }

                    dictionary.Add(info.VariableName, excelVariableAttribute);
                }
            }
        }


        private string GenParseMapJs()
        {
            try
            {
                if (!GetMapInfo(out Dictionary<string, GenParseJsInfo> mapInfos))
                {
                    //excel未定义词典
                    return null;
                }

                if (!ProtoBuffAssembleEditor.GetProtoBuffStatic(DataItemName, out IMessage itemMessage, out Type type))
                {
                    ProtoDebug.LogError($"解析类生成错误，errssss");
                    return null;
                }

                Dictionary<string, string> fields = new();
                foreach (var field in itemMessage.Descriptor.Fields.InFieldNumberOrder())
                {
                    fields.Add(field.Name, field.PropertyName);
                }

                List<string> parseJs = new();
                string jsStr = null;
                List<string> dictNames = new();
                List<string> dictNameIfs = new();
                List<string> initFunList = new();
                List<string> funcNameList = new();
                List<string> getMapCS = new();
                VariableInfo.ExcelVariableInfoAttribute infoAttribute = null;
                List<VariableInfo.ExcelVariableInfoAttribute> attributes =
                    new List<VariableInfo.ExcelVariableInfoAttribute>();
                Stack<string> childMapNames = new();
                string baseMapName = null;
                foreach (var mapInfo in mapInfos)
                {
                    if (mapInfo.Value.ExcelVariableInfo != null)
                    {
                        //去除最后一个特别处理
                        int count = mapInfo.Value.ExcelVariableInfo.Count - 1;

                        if (count > 0)
                        {
                            string dictStr = _dictionaryName;

                            string variableName = null;
                            baseMapName = mapInfo.Key;
                            parseJs.Clear();
                            attributes.Clear();
                            childMapNames.Clear();
                            infoAttribute = mapInfo.Value.ExcelVariableInfo[count];
                            string childMapName = string.Format(_dictionaryName, infoAttribute.OriginVariableType,
                                DataItemName);
                            childMapNames.Push(childMapName);
                            for (int i = 0; i < count; i++)
                            {
                                int index = count - i - 1;
                                if (index > 0) //丢弃最后一个
                                {
                                    infoAttribute = mapInfo.Value.ExcelVariableInfo[index];
                                    childMapName = string.Format(_dictionaryName, infoAttribute.OriginVariableType,
                                        childMapName);
                                    childMapNames.Push(childMapName);
                                }

                                infoAttribute = mapInfo.Value.ExcelVariableInfo[i];
                                dictStr = string.Format(dictStr, infoAttribute.OriginVariableType,
                                    _dictionaryName);
                                if (fields.TryGetValue(infoAttribute.VariableName, out variableName))
                                {
                                    string result =
                                        csCtruct.ParseDictionary.Replace("####VariableName####", variableName);
                                    result = result.Replace("####Base####", baseMapName);
                                    parseJs.Add(result);
                                    baseMapName = variableName;
                                }

                                attributes.Add(infoAttribute);
                                getMapCS.Add(GenMapGetFunction(mapInfo.Key, attributes, false));
                            }

                            infoAttribute = mapInfo.Value.ExcelVariableInfo[count];
                            attributes.Add(infoAttribute);
                            getMapCS.Add(GenMapGetFunction(mapInfo.Key, attributes, true));
                            dictStr = string.Format(dictStr, infoAttribute.OriginVariableType, DataItemName);
                            dictNames.Add(csCtruct.BodyDcit.Replace("####DictType####",dictStr).Replace("####DictName####",mapInfo.Key));
                            dictNameIfs.Add(csCtruct.BodyIf.Replace("####DictType####",dictStr).Replace("####DictName####",mapInfo.Key));
                            //拼接方法名称
                            jsStr = csCtruct.FunctionName.Replace("####DictionaryName####", mapInfo.Key);
                            funcNameList.Add($"\t\t\t{jsStr}(item);");
                            // 插入方法名称
                            jsStr = csCtruct.FunctionBody.Replace("####FunctionName####", jsStr);
                            jsStr = jsStr.Replace("####ClassName####", DataItemName);
                            //拼接词典解析
                            for (int i = 0; i < parseJs.Count; i++)
                            {
                                parseJs[i] = parseJs[i].Replace("####ChildMap####", childMapNames.Pop());
                            }

                            jsStr = jsStr.Replace("####Body####", string.Join(Environment.NewLine, parseJs));
                            jsStr = jsStr.Replace("####Last####", baseMapName);
                            if (fields.TryGetValue(mapInfo.Value.ExcelVariableInfo[count].VariableName,
                                    out variableName))
                            {
                                jsStr = jsStr.Replace("####VariableName####",
                                    variableName);
                            }

                            initFunList.Add(jsStr);
                        }
                    }
                }
                jsStr = csCtruct.Body.Replace("####ClassName####", ClassName);
                jsStr = jsStr.Replace("####DLL####", ProtoBuffPackageConfig.ProtoInterface);
                jsStr = jsStr.Replace("####DictionaryVariable####", string.Join("\n", dictNames));
                jsStr = jsStr.Replace("####BodyIfStr####", string.Join("\n", dictNameIfs));
                jsStr = jsStr.Replace("####Loop####", string.Join("\n", funcNameList));
                jsStr = jsStr.Replace("####InitDictFun####", string.Join("\n", initFunList));
                jsStr = jsStr.Replace("####GetDictFun####", string.Join("\n", getMapCS));

                return jsStr;
            }
            catch (Exception e)
            {
                ProtoDebug.LogError($"解析类生成错误，err：{e.Message}");
            }

            return null;
        }

        private bool GetMapInfo(out Dictionary<string, GenParseJsInfo> mapInfos)
        {
            mapInfos = new();
            foreach (var info in variableInfos)
            {
                if (info.CurExcelVariableInfoAll != null)
                {
                    if (info.CurExcelVariableInfoAll.ExcelVariableMap != null)
                    {
                        foreach (var variableMap in info.CurExcelVariableInfoAll.ExcelVariableMap)
                        {
                            string mapName = null;
                            if (variableMap.Value.IsMaKey)
                            {
                                if (variableMap.Value.IsPrimaryKey)
                                {
                                    mapName = $"{ClassName}Dict";
                                    variableMap.Value.ExcelMapVariableName = mapName;
                                }
                                else
                                {
                                    mapName = variableMap.Value.ExcelMapVariableName;
                                }

                                if (!mapInfos.TryGetValue(mapName, out var mapInfo))
                                {
                                    mapInfo = new GenParseJsInfo();
                                    mapInfos.Add(mapName, mapInfo);
                                }

                                mapInfo.ExcelVariableInfo.Add(variableMap.Value);
                            }
                        }
                    }
                }
            }

            if (mapInfos.Count <= 0)
            {
                mapInfos = new();
                return false;
            }

            return true;
        }

        private string GenMapGetFunction(string curDictName, List<VariableInfo.ExcelVariableInfoAttribute> attributes,
            bool isEnd = false)
        {
            try
            {
                string mapName = curDictName;
                string getDictionary = csCtruct.GetDictionary;
                string result = null;
                string gettring = null;
                for (int i = 0; i < attributes.Count; i++)
                {
                    var attribute = attributes[i];
                    string variableName = attribute.VariableName;
                    result = getDictionary.Replace("####DictName####", mapName);
                    result = result.Replace("####KeyName####", variableName);
                    result = result.Replace("####ValueName####", variableName);
                    if (gettring != null)
                    {
                        gettring = gettring.Replace("####GetBody####", result);
                    }
                    else
                    {
                        gettring = result;
                    }

                    mapName = variableName;
                }

                result = csCtruct.GetDictionaryReturn.Replace("####ValueName####", mapName);
                if (isEnd)
                {
                    getDictionary = "IMessage";
                }
                else
                {
                    getDictionary = "IDictionary";
                }

                result = result.Replace("####TType####", getDictionary);
                if (gettring != null)
                {
                    //词典对象获取代码主体
                    gettring = gettring.Replace("####GetBody####", result);
                    //方法体拼接
                    List<string> paramsList = attributes.ConvertAll(x => $"{x.OriginVariableType} {x.VariableName}");
                    result = csCtruct.GetMap.Replace("####Params####", string.Join(", ", paramsList));
                    result = result.Replace("####DictName####", curDictName);
                    result = result.Replace("####TType####", getDictionary);
                    gettring = result.Replace("####GetBody####", gettring);
                    return gettring;
                }
            }
            catch (Exception e)
            {
                ProtoDebug.LogError($"生成获取Map方法错误，err：{e.Message}");
            }

            return null;
        }

        /// <summary>
        /// 保存数据
        /// </summary>
        /// <returns></returns>
        public bool SaveData()
        {
            if (!Directory.Exists(dataPath))
            {
                Directory.CreateDirectory(dataPath);
            }

            try
            {
                using (FileStream writer = File.Create($"{dataPath}/{ClassName}.{DataaExe}"))
                {
                    using (CodedOutputStream codedOutputStream = new CodedOutputStream(writer))
                    {
                        excelData.WriteTo(codedOutputStream);
                        return true;
                    }
                }
            }
            catch (Exception e)
            {
                ProtoDebug.LogError($"{ClassName}数据写入错误，err:{e}");
            }

            return false;
        }

        /// <summary>
        /// 获取类名列表
        /// </summary>
        private string ClassNameList =>
            $@"message {ClassName}
{{
  map<string,{ExcelVariableAttributeName}> {VariableInfoAllMap} = 1;
  repeated {DataItemName} {DataListName} = 2;
  map<int32,{DataItemName}> {DataDictName} = 3;
}}";

        public ObjectInfo CurObjectInfo { get; set; } = new ObjectInfo();
        public bool IsDataClass { get; set; }

        public override string ToString()
        {
            return @$"{ClassNameList}
message {DataItemName}{{
 {string.Join('\n', variableInfos)}
}}";
        }

        public void GenPaserCS()
        {
            if (!Directory.Exists(parserPath))
            {
                Directory.CreateDirectory(parserPath);
            }

            try
            {
                string content = GenParseMapJs();
                if (!string.IsNullOrEmpty(content))
                {
                    File.WriteAllText($"{parserPath}/{ClassName}.cs", content);
                }
            }
            catch (Exception e)
            {
                ProtoDebug.LogError($"{ClassName}数据写入错误，err:{e}");
            }
        }
    }
}